package com.fz.study.project.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fz.study.project.entity.Stock;
import com.fz.study.project.mapper.StockMapper;
import com.fz.study.project.service.IStockService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author author
 * @since 2024-02-21
 */
//@Scope表示开启多例模式
//@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements IStockService {
    @Autowired
    private StockMapper mapper;
//    这个是jvm带的锁，一般不推荐使用
    private final ReentrantLock lock = new ReentrantLock();
//    @Transactional(isolation = Isolation.READ_UNCOMMITTED)表示读取事务未提交的数据信息
//    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
//    @Transactional
//        mapper.updateStock("1001",1);表示一个sql方式
    /*
    * 一个sql的方式解决了事务，多例、多服务器的问题，但是不能解决多处相产品之间的筛选
    * 例如：同一个商品在北京和上海的存货是不一样的，但是一个sql只能针对一处地方
    * 无法记录数据前后变化
    * */
    @Override
    public void deduct() {
        mapper.updateStock("1001",1);
    }
//    使用jvm自带的锁
    /*
    * 在处理事务、多例或者多服务器时会出现异常，只能解决单例单多用户同时访问
    * */
    public void deduct1(){
//        打开锁
        lock.lock();
        try {
            Stock one = lambdaQuery().like(Stock::getProductCode, 1001).one();
            if (one != null && one.getCount() > 0) {
                one.setCount(one.getCount() - 1);
                updateById(one);
            }
        } finally {
//            关闭锁
            lock.unlock();
        }
    }

//    使用synchronized定义锁
/*
 * 在处理事务、多例或者多服务器时会出现异常
 * */
    public synchronized void deduct2(){
        Stock one = lambdaQuery().like(Stock::getProductCode, 1001).one();
        if (one != null && one.getCount() > 0) {
            one.setCount(one.getCount() - 1);
            updateById(one);
        }
    }

    /*
    * mysql悲观锁
    * 悲观锁也是能解决事务、多例、多服务器的问题，但是性能相对要低一些，
    * 同时，悲观锁可能会出现死锁的情况,加锁的顺序要一致
    * 悲观锁在一个事务开启之后会直接进行行锁，也就是说会对整个表进行锁定，之后当该锁释放或者提交之后才会进行其他的
    * sql事务
    *
    * 库操作要统一：全部都是select ... for update 格式
    *
    * 悲观锁在对数据进行加锁之后，普通的查询是能直接查询到原有数据
    * */
    @Transactional
    public void deduct3(){
        List<Stock> stocks = mapper.queryStock("1001");
        Stock stock = stocks.get(0);
        if (!Objects.isNull(stock) && stock.getCount()>=1){
            stock.setCount(stock.getCount() -1);
            updateById(stock);
        }

    }


    /*
    * 乐观锁
    * 乐观锁是需要依赖时间戳或者version版本号 CAS机制：比较并交换
    * 乐观锁能解决事务、多例、多服务器的问题，但是乐观锁的主要问题是会出现ABA的问题
    * ABA简单来说就是在对乐观字段做判断之后在进行其他业务时，其他用户可以对乐观字段作修改操作，在其他业务完成之后
    * 再在作乐观字段验证时，将乐观字段恢复为原先的数据，乐观校验也同样会成功
    *
    * 高并发情况下，性能极低
    * 读写分离情况下不可靠
    *
    * 不能添加事务注解
    * */
//    @Transactional
    public void deduct4(){
        List<Stock> stocks = lambdaQuery().eq(Stock::getProductCode, "1001").list();
        Stock stock = stocks.get(0);
        if (!Objects.isNull(stock) && stock.getCount()>=1){
            stock.setCount(stock.getCount() -1);
            Integer version = stock.getVersion();
            stock.setVersion(version+1);
            if (mapper.update(stock,new LambdaUpdateWrapper<Stock>()
                    .eq(Stock::getId,stock.getId())
                    .eq(Stock::getVersion,version))==0) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                deduct4();
            }
        }

    }

}
